package BuyerAgent;

import jade.content.AgentAction;
import jade.content.ContentElement;
import jade.content.onto.basic.Action;
import jade.content.onto.basic.Result;
import jade.core.AID;
import jade.core.behaviours.OneShotBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.util.leap.Serializable;

import java.util.ArrayList;

import javax.management.Query;
import javax.swing.plaf.metal.MetalIconFactory.TreeFolderIcon;

import Ontologies.FruitTreeVocabulary;
import Ontologies.PurchaseAction;
import Ontologies.PurchaseResponse;
import Ontologies.QueryAction;
import Ontologies.TreeWithPrice;

/**
 * This class is the behavior of buyer agent in which it will try to buy trees
 * in its wish list from the sellers that offer the best price. <br>
 * If the wish list has not been empty and the number of times trying to buy is
 * not greater than MAX_NUM_TRY_TO_BUY, try to run this behavior again,
 * exitValue = 0 When wish list is empty or numTryToBuy == MAX_NUM_TRY_TO_BUY,
 * change to next behavior state moving back home container, exitValue = 1
 * 
 * @author DUY HAN PHAN
 */
public class BuyTreesBehaviour extends OneShotBehaviour {
	private volatile Buyer buyerAgent;
	private volatile java.util.ArrayList<Tree> wishList;
	private volatile StringBuilder purchaseReport;
	private int exitValue = 0;
	private ListSellersForATree listSellersForATree = null;
	private ArrayList<ListSellersForATree> ListSellersForAllTreesInWishList = null;

	public BuyTreesBehaviour(Buyer buyerAgent) {
		this.buyerAgent = buyerAgent;
		wishList = buyerAgent.wishList;
		purchaseReport = buyerAgent.purchaseReport;
	}

	private boolean isSatisfiedStopCondition() {
		boolean isWishListEmpty = wishList.isEmpty();
		int maxNumAttempts = buyerAgent.MAX_NUM_TRY_TO_BUY;
		int curNumAttempts = buyerAgent.numTryToBuy;
		if (isWishListEmpty || curNumAttempts >= maxNumAttempts) {
			return true;
		}
		return false;
	}

	@Override
	public void action() {
		// TODO buy trees, best price is first priority
		// if (isSatisfiedStopCondition()) {
		// exitValue = 1;
		// return;
		// }

		// register again after migrating to main container -kai
		myAgent.getContentManager().registerLanguage(((Buyer) myAgent).codec);
		myAgent.getContentManager().registerOntology(((Buyer) myAgent).ontology);

		buyerAgent.numTryToBuy = buyerAgent.numTryToBuy + 1;
		purchaseReport.append("\n\n*** numTryToBuy = " + buyerAgent.numTryToBuy + " ***");
		ListSellersForAllTreesInWishList = new ArrayList<BuyTreesBehaviour.ListSellersForATree>();
		try {
			for (int i = 0; i < buyerAgent.wishList.size(); i++) {
				int initialWishListSize = buyerAgent.wishList.size();
				Tree treeInWishList = buyerAgent.wishList.get(i);
				listSellersForATree = new ListSellersForATree(treeInWishList);

				purchaseReport.append("\n\n** Trying to buy tree: " + treeInWishList.getCommonName()
						+ ", in category: " + treeInWishList.getCategory());
				// purchaseReport.append("\n ==> wish list size = " +
				// buyerAgent.wishList.size() + "; i = " + i);
				// Update the list of seller agents
				purchaseReport.append("\n* Update the list of seller agents for category: "
						+ treeInWishList.getCategory() + "\n");
				DFAgentDescription template = new DFAgentDescription();
				ServiceDescription sd = new ServiceDescription();
				String serviceType = treeInWishList.getCategory() + FruitTreeVocabulary.SELLING;
				sd.setType(serviceType);
				template.addServices(sd);

				AID[] sellerAgents = new AID[0];// The list of known seller
												// agents for
				// particular category
				DFAgentDescription[] result = DFService.search(myAgent, template);
				if (result.length > 0) {
					purchaseReport.append("* Found the following seller agents: ");
					sellerAgents = new AID[result.length];
					for (int j = 0; j < result.length; j++) {
						sellerAgents[j] = result[j].getName();
						purchaseReport.append("  " + sellerAgents[j].getLocalName() + "  ");
					}
				} else {
					purchaseReport.append("* Cannot find any seller agent with category: "
							+ treeInWishList.getCategory());
				}
				// ask for the price and quantity of the tree in wish list from
				// all seller agents in this category
				if (sellerAgents.length > 0) {
					purchaseReport.append("\n* Asking for the prices of tree " + treeInWishList.getCommonName()
							+ " from above sellers:");
					for (int j = 0; j < sellerAgents.length; j++) {
						QueryAction queryAction = new QueryAction();
						queryAction.setCommonName(treeInWishList.getCommonName());
						queryAction.setLatinName(treeInWishList.getLatinName());
						purchaseReport.append("\n  Asking " + sellerAgents[j].getLocalName());
						sendMessage(ACLMessage.REQUEST, queryAction, sellerAgents[j]);
					}

					// Collected enough information from sellers of this tree
					// now it's time to add it to
					// ListSellersForAllTreesInWishList
					if (listSellersForATree.sellers.size() > 0) {
						ListSellersForAllTreesInWishList.add(listSellersForATree);
					}
				}
			}// end of for for (int i = 0; i < buyerAgent.wishList.size(); i++)

			if (ListSellersForAllTreesInWishList.size() > 0) {
				for (int i = 0; i < ListSellersForAllTreesInWishList.size(); i++) {
					ListSellersForATree listSellersForATreeInWishList = ListSellersForAllTreesInWishList.get(i);
					if (listSellersForATreeInWishList.sellers.size() > 0) {
						listSellersForATreeInWishList.buyTree();
					}
				}
			}

		} catch (FIPAException fe) {
			fe.printStackTrace();
		}

		try {
			Thread.sleep(buyerAgent.INTERVAL_BETWEEN_ATTEMPTS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		if (isSatisfiedStopCondition()) {
			exitValue = 1;
			return;
		} else {
			exitValue = 0;
			return;
		}
	}

	void requestPriceOfAllSellers(final AID[] allSellerAgents, Tree treeInWishList) {

	}

	private void sendMessage(int performative, AgentAction action, AID sellerAID) {
		if (sellerAID == null) {
			System.out.println("BuyerAID == null");
			return;
		}
		ACLMessage msg = new ACLMessage(performative);
		msg.setLanguage(buyerAgent.codec.getName());
		msg.setOntology(buyerAgent.ontology.getName());
		try {
			buyerAgent.getContentManager().fillContent(msg, new Action(sellerAID, action));
			msg.addReceiver(sellerAID);
			buyerAgent.send(msg);
			System.out.println("Waiting for answers from seller... Please wait!");
			receiveResponse(sellerAID);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Receive and process responses from sellers
	 * 
	 * @param sellerAID
	 */
	private void receiveResponse(AID sellerAID) {
		ACLMessage msg = buyerAgent.blockingReceive(MessageTemplate.MatchSender(sellerAID));
		if (msg == null) {
			purchaseReport.append("\n    --> reponse from seller " + sellerAID.getLocalName() + " is NULL");
			block();
			return;
		}
		if (msg.getPerformative() == ACLMessage.NOT_UNDERSTOOD) {
			System.out.println("\n\n\tResponse from server: NOT UNDERSTOOD!");
			purchaseReport.append("\n   --> reponse from seller is NOT UNDERSTOOD");
		} else if (msg.getPerformative() != ACLMessage.INFORM) {
			System.out.println("\nUnexpected msg from server!");
			purchaseReport.append("\n   --> reponse from seller is Unexpected");
		} else {
			try {
				ContentElement content = buyerAgent.getContentManager().extractContent(msg);
				if (content instanceof Result) {
					Result result = (Result) content;
					if (result.getValue() instanceof TreeWithPrice) {
						TreeWithPrice treeWithPrice = (TreeWithPrice) result.getValue();
						if (treeWithPrice == null) {
							purchaseReport.append("  --> " + sellerAID.getLocalName() + " does not have tree : "
									+ treeWithPrice.getCommonName());
							block();
							return;
						}
						// check if seller does not have such tree
						if (treeWithPrice.getQuantity() < 1) {
							purchaseReport.append("  --> " + sellerAID.getLocalName() + " does not have tree : "
									+ treeWithPrice.getCommonName());
							block();
							return;
						}
						purchaseReport.append("  --> " + sellerAID.getLocalName() + " Offers Price: $"
								+ treeWithPrice.getUnitPrice());
						// TODO
						listSellersForATree.sellers.add(new SellerAndTree(sellerAID, treeWithPrice));
					} else if (result.getValue() instanceof PurchaseResponse) {
						PurchaseResponse purchaseResponse = (PurchaseResponse) result.getValue();
						System.out.println("purchaseResponse: " + purchaseResponse.getErrorMessage());
						// TODO
						if (purchaseResponse.getErrorCode() == 0) {
							// TODO purchase successfully, update the wish list,
							// update the purchase report
							// NOTE: Can't execute another query here, because
							// it is still waiting for the response from other
							// sellers for a kind of tree
							int quantityPurchaseResponse = purchaseResponse.getQuantity();
							purchaseReport.append("\n --> Bought from " + sellerAID.getLocalName() + " "
									+ " Quantity = " + quantityPurchaseResponse + "; Tree Common Name = "
									+ purchaseResponse.getCommonName());
							for (int i = 0; i < wishList.size(); i++) {
								Tree tree = wishList.get(i);
								if (tree.getCommonName().equalsIgnoreCase(purchaseResponse.getCommonName())) {
									if (tree.getQuantity() == quantityPurchaseResponse) {
										wishList.remove(tree);
										// purchaseReport.append("\n --> (tree.getQuantity() == quantityPurchaseResponse)");
										break;
									} else if (tree.getQuantity() > purchaseResponse.getQuantity()) {
										tree.setQuantity(tree.getQuantity() - quantityPurchaseResponse);
										// purchaseReport.append("\n --> (tree.getQuantity() > quantityPurchaseResponse)");
										break;
									} else if (tree.getQuantity() < quantityPurchaseResponse) {
										// purchaseReport.append("\n --> (tree.getQuantity() < quantityPurchaseResponse)");
										break;
									}
								}
							}
						} else if (purchaseResponse.getErrorCode() == 1) {
							// TODO sold out --> only update purchase report
							purchaseReport.append("\n" + sellerAID.getLocalName() + " does not have any "
									+ purchaseResponse.getCommonName() + " anymore!!");
						}
					} else
						System.out.println("\n\tUnexpected result from server!");
				} else {
					System.out.println("\n\tUnable de decode response from server!");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public int onEnd() {
		return exitValue;
	}

	/**
	 * @author DUY HAN PHAN This classes store the seller AID and the its
	 *         information about a particular type of tree that it has
	 */
	class SellerAndTree implements Serializable {
		public AID seller;
		public TreeWithPrice treeWithPrice;

		public SellerAndTree(AID seller, TreeWithPrice treeWithPrice) {
			super();
			this.seller = seller;
			this.treeWithPrice = treeWithPrice;
		}
	}

	/**
	 * @author DUY HAN PHAN This class stores seller AID and correspondent
	 *         purchase action
	 */
	class SellerAndPurchaseAction implements Serializable {
		public AID seller;
		public PurchaseAction purchaseAction;

		public SellerAndPurchaseAction(AID seller, PurchaseAction purchaseAction) {
			super();
			this.seller = seller;
			this.purchaseAction = purchaseAction;
		}
	}

	/**
	 * @author DUY HAN PHAN This class stores all the sellers that offer a
	 *         particular kind of tree
	 */
	class ListSellersForATree implements Serializable {
		public ArrayList<SellerAndTree> sellers;
		public volatile Tree treeFromWishList;

		public ListSellersForATree(Tree treeFromWishList) {
			this.treeFromWishList = treeFromWishList;
			sellers = new ArrayList<BuyTreesBehaviour.SellerAndTree>();
		}

		/**
		 * This method performs the bubble sort
		 * 
		 * @param numbersToSort
		 */
		private void bubbleSort(ArrayList<SellerAndTree> numbersToSort) {

			SellerAndTree temp = null;
			for (int i = 0; i < numbersToSort.size() - 1; i++) {
				for (int j = 1; j < (numbersToSort.size() - i); j++) {
					if (numbersToSort.get(j - 1).treeWithPrice.getUnitPrice() > numbersToSort.get(j).treeWithPrice
							.getUnitPrice()) {
						// swap the elements!
						temp = numbersToSort.get(j - 1);
						numbersToSort.set(j - 1, numbersToSort.get(j));
						numbersToSort.set(j, temp);
					}

				}
			}
		}

		/**
		 * @return sorted sellers, price from low to high
		 */
		private ArrayList<SellerAndTree> sortSellersByPrice() {
			ArrayList<SellerAndTree> sortedSellers = sellers;
			bubbleSort(sortedSellers);
			return sortedSellers;
		}

		private ArrayList<SellerAndPurchaseAction> getNecessaryPurchaseActions() {
			if (sellers.size() < 1) {
				return null;
			}
			ArrayList<SellerAndPurchaseAction> purchaseActions = new ArrayList<SellerAndPurchaseAction>();
			// TODO
			PurchaseAction purchaseAction = null;
			SellerAndPurchaseAction sellerAndPurchaseAction = null;

			ArrayList<SellerAndTree> sortedSellers = sortSellersByPrice();
			int quantityWish = treeFromWishList.getQuantity();
			int quantityBuy = 0;
			for (int i = 0; i < sortedSellers.size(); i++) {
				SellerAndTree sellerAndTree = sortedSellers.get(i);
				if (quantityBuy < quantityWish) {
					if (quantityBuy + sellerAndTree.treeWithPrice.getQuantity() > quantityWish) {
						// TODO
						purchaseAction = new PurchaseAction(treeFromWishList.getCommonName(),
								treeFromWishList.getLatinName(), quantityWish - quantityBuy);
						sellerAndPurchaseAction = new SellerAndPurchaseAction(sellerAndTree.seller, purchaseAction);
						purchaseActions.add(sellerAndPurchaseAction);
						break;
					} else if (quantityBuy + sellerAndTree.treeWithPrice.getQuantity() == quantityWish) {
						// TODO
						purchaseAction = new PurchaseAction(treeFromWishList.getCommonName(),
								treeFromWishList.getLatinName(), sellerAndTree.treeWithPrice.getQuantity());
						sellerAndPurchaseAction = new SellerAndPurchaseAction(sellerAndTree.seller, purchaseAction);
						purchaseActions.add(sellerAndPurchaseAction);
						break;
					} else if (quantityBuy + sellerAndTree.treeWithPrice.getQuantity() < quantityWish) {
						// TODO
						purchaseAction = new PurchaseAction(treeFromWishList.getCommonName(),
								treeFromWishList.getLatinName(), sellerAndTree.treeWithPrice.getQuantity());
						sellerAndPurchaseAction = new SellerAndPurchaseAction(sellerAndTree.seller, purchaseAction);
						purchaseActions.add(sellerAndPurchaseAction);
						// fix bug buy all trees of sellers
						quantityBuy = quantityBuy + sellerAndTree.treeWithPrice.getQuantity();
						continue;
					}
				} else {
					break;
				}
			}

			return purchaseActions;
		}

		public void buyTree() {
			ArrayList<SellerAndPurchaseAction> SellerAndPurchaseActionList = getNecessaryPurchaseActions();
			if (SellerAndPurchaseActionList == null) {
				return;
			}
			SellerAndPurchaseAction sellerAndPurchaseAction = null;
			for (int i = 0; i < SellerAndPurchaseActionList.size(); i++) {
				// TODO
				sellerAndPurchaseAction = SellerAndPurchaseActionList.get(i);
				AID sellerAID = sellerAndPurchaseAction.seller;
				PurchaseAction purchaseAction = sellerAndPurchaseAction.purchaseAction;
				purchaseReport.append("\n* Sending purchase request to " + sellerAID.getLocalName() + "; Quantity = "
						+ purchaseAction.getQuantity() + "; TreeCommonNamee = " + purchaseAction.getCommonName());
				sendMessage(ACLMessage.REQUEST, purchaseAction, sellerAID);
			}
		}
	}
}
